package com.mindskip.xzs.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mindskip.xzs.component.QywxComponent;
import com.mindskip.xzs.constant.CommonRedisKey;
import com.mindskip.xzs.domain.*;
import com.mindskip.xzs.domain.enums.*;
import com.mindskip.xzs.event.UserEvent;
import com.mindskip.xzs.exception.BusinessException;
import com.mindskip.xzs.repository.ExamMapper;
import com.mindskip.xzs.repository.ExamUserMapper;
import com.mindskip.xzs.repository.NewUserMapper;
import com.mindskip.xzs.service.ExamService;
import com.mindskip.xzs.service.ExamUserService;
import com.mindskip.xzs.service.NewExamPaperAnswerService;
import com.mindskip.xzs.service.NewExamPaperService;
import com.mindskip.xzs.viewmodel.IdNameKV;
import com.mindskip.xzs.viewmodel.admin.exam.*;
import com.mindskip.xzs.viewmodel.wx.student.qywx.MarkdownNotice;
import com.mindskip.xzs.viewmodel.wx.student.qywx.MiniProgramNotice;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {

    @Resource
    private NewExamPaperService newExamPaperService;
    @Resource
    private ExamUserService examUserService;
    @Resource
    private ExamUserMapper examUserMapper;
    @Resource
    private QywxComponent qywxComponent;
    @Resource
    private NewUserMapper newUserMapper;
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private NewExamPaperAnswerService newExamPaperAnswerService;

    /**
     * 小程序自身ID
     */
    @Value("${xcx.appid}")
    private String appid;
    /**
     * agentid
     * 每个应用都有唯一的agentid。在管理后台->“应用与小程序”->“应用”，点进某个应用，即可看到agentid。
     */
    @Value("${qywx.agentId}")
    private Long agentId;


    /**
     * 更新缓存
     */
    @Override
    public void updateCategorysInCache() {
        List<String> categoryList = baseMapper.queryCategoryList(null);
        if (CollectionUtil.isNotEmpty(categoryList)) {
            redisTemplate.opsForValue().set(CommonRedisKey.ALL_EXAM_CATEGORY_KEY, categoryList, 30, TimeUnit.DAYS);
        }
    }

    @Override
    public List<String> queryCategoryListInCache(String keyword) {
        List<String> categoryList = (List<String>) redisTemplate.opsForValue().get(CommonRedisKey.ALL_EXAM_CATEGORY_KEY);
        if (StringUtils.isNotBlank(keyword) && categoryList != null) {
            categoryList = categoryList.stream().filter(category -> category.contains(keyword)).collect(Collectors.toList());
        }
        return categoryList;
    }



    @Override
    public PageInfo<PageExamRes> queryExamList(PageExamReq req) {
        // 跟进薛定谔的猫原理，更新考试状态
        List<Exam> allExams = baseMapper.selectList(new LambdaQueryWrapper<Exam>().eq(Exam::getDeleted, false));
        if (CollectionUtil.isNotEmpty(allExams)) {
            LinkedList<Exam> updateExams = new LinkedList<>();
            allExams.forEach(exam -> {
                // 只用关心时段考试
                if (ExamTypeEnum.TimeLimit.getCode().equals(exam.getExamType())) {
                    // 是否在考试
                    if (exam.getLimitStartTime().isBefore(LocalDateTime.now()) && exam.getLimitEndTime().isAfter(LocalDateTime.now()) && exam.getStatus().equals(ExamStatusEnum.ING.getCode())) {
                        exam.setStatus(ExamStatusEnum.ING.getCode());
                        updateExams.add(exam);
                    }
                    // 是否结束 只用关心考试结束时间
                    if (exam.getLimitEndTime().isBefore(LocalDateTime.now())) {
                        exam.setStatus(ExamStatusEnum.DONE.getCode());
                        updateExams.add(exam);
                    }
                }
            });
            if (CollectionUtil.isNotEmpty(updateExams)) {
                updateBatchById(updateExams);
            }
        }

        // 分页查询
        PageHelper.startPage(req.getPageNum(), req.getPageSize(), "e.id desc");
        List<PageExamRes> pageExamRes = baseMapper.pageExamList(req);
        if (CollectionUtil.isEmpty(pageExamRes)) {
            return new PageInfo<>();
        }
        pageExamRes.forEach(res -> {
            if (res.getLimitStartTime() != null) {
                res.setExamStartTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(res.getLimitStartTime()));
            } else {
                res.setExamStartTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(res.getExamCreateTime()));
            }
            if (res.getLimitEndTime() != null) {
                res.setExamEndTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(res.getLimitEndTime()));
            } else {
                res.setExamEndTime("长期");
            }
        });
        return new PageInfo<>(pageExamRes);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addExam(AddExamReq addExamReq, Integer operUserId) {
/*        Exam existExam = baseMapper.selectOne(
                new LambdaQueryWrapper<Exam>()
                        .eq(Exam::getPaperId, addExamReq.getPaperId())
                        .eq(Exam::getDeleted, false)
                        .ne(Exam::getStatus, ExamStatusEnum.DONE.getCode()));
        if (existExam != null) {
            throw new BusinessException("发布考试失败，已有编号为" + addExamReq.getPaperId() + "的试卷" + ExamStatusEnum.of(existExam.getStatus()).getName());
        }*/

        NewExamPaper examPaper = newExamPaperService.getById(addExamReq.getPaperId());
        if (examPaper == null) {
            throw new BusinessException("编号为" + addExamReq.getPaperId() + "的试卷不存在，请检查核对");
        }

        Integer status = null;
        ExamTypeEnum examTypeEnum = ExamTypeEnum.of(addExamReq.getExamType());
        LocalDateTime limitStartTime = null;
        LocalDateTime limitEndTime= null;
        switch (examTypeEnum) {
            // 固定试卷
            case Fixed:
                // 固定试卷，考试状态为考试中，考试状态
                status = ExamStatusEnum.ING.getCode();
                break;
            // 时段试卷
            case TimeLimit:
                if (addExamReq.getLimitDateTime() != null && StringUtils.isNotBlank(addExamReq.getLimitDateTime().get(0))) {
                    limitStartTime = LocalDateTime.parse(addExamReq.getLimitDateTime().get(0), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                }else {
                    throw new BusinessException("考试限制时间不能为空");
                }
                if (addExamReq.getLimitDateTime() != null && StringUtils.isNotBlank(addExamReq.getLimitDateTime().get(1))) {
                    limitEndTime = LocalDateTime.parse(addExamReq.getLimitDateTime().get(1), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                }else {
                    throw new BusinessException("考试限制时间不能为空");
                }
                // 判断时间是否合规
                if (limitEndTime.isBefore(LocalDateTime.now())) {
                    log.error("考试时段已过，不可发布考试，请调整考试时间段，msg：{}", JSON.toJSONString(examPaper));
                    throw new BusinessException("考试时段已过，不可发布考试，请调整考试时间段");
                }
                if (limitEndTime.isBefore(limitStartTime)) {
                    log.error("考试时段不合规，开始考试时间不能在结束考试之后，不可发布考试，请调整考试时间段，msg：{}", JSON.toJSONString(examPaper));
                    throw new BusinessException("考试时段不合规，开始考试时间不能在结束考试之后，不可发布考试，请调整考试时间段");
                }
                if (limitStartTime.isBefore(LocalDateTime.now())) {
                    // 开始时间已过，考试状态为考试中
                    status = ExamStatusEnum.ING.getCode();
                } else {
                    // 开始时间未到，考试状态为等待考试
                    status = ExamStatusEnum.WAIT.getCode();
                }
                break;
            default:
                break;
        }
        // 插入考试表
        Exam exam = new Exam()
                .setLimitStartTime(limitStartTime)
                .setLimitEndTime(limitEndTime);
        BeanUtils.copyProperties(addExamReq, exam);
        baseMapper.insert(
                exam.setStatus(status)
                        .setCreateUser(operUserId)
                        .setDeleted(false)
        );

        // 插入考试-用户表
        List<Long> userIds = newUserMapper.selectList(
                new LambdaUpdateWrapper<User>()
                        .in(User::getQywxId, addExamReq.getUserQywxIds())
                        .eq(User::getStatus, UserStatusEnum.Enable.getCode())
        ).stream().map(user -> user.getId().longValue()).collect(Collectors.toList());
        HashSet<ExamUser> examUsers = new HashSet<>();
        userIds.forEach(userId -> {
            examUsers.add(
                    new ExamUser()
                            .setExamId(exam.getId())
                            .setUserId(userId)
                            .setStatus(ExamUserStatusEnum.NOTICED.getCode())
            );
        });
        examUserService.saveBatch(examUsers);

        // 通知员工考试
        if (addExamReq.getNotice() == null || addExamReq.getNotice()) {
            List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().select(User::getQywxId).in(User::getId, userIds));
            if (CollectionUtil.isEmpty(users)) {
                log.error("通知员工考试，查询对应考试用户为空，msg：{}", JSON.toJSONString(addExamReq));
                throw new BusinessException("未查到对应考试用户");
            }
            String examStartTime = null;
            String examEndTime = null;
            switch (examTypeEnum) {
                // 固定试卷
                case Fixed:
                    // 固定试卷，
                    examStartTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = "长期";
                    break;
                // 时段试卷
                case TimeLimit:
                    examStartTime = limitStartTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = limitEndTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    break;
                default:
                    break;
            }
            qywxComponent.sendMsg(
                    new MiniProgramNotice()
                            .setTouser(users.stream().map(User::getQywxId).distinct().collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MINI_PROGRAM_NOTICE.getMsgtype())
                            .setEnable_id_trans(0)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMiniprogram_notice(
                                    new MiniProgramNotice.Notice()
                                            .setAppid(appid)
                                            .setPage(String.format("pages/exam/do/index?id=%s&examId=%s", addExamReq.getPaperId(), exam.getId()))
                                            .setTitle("考试通知")
                                            .setDescription(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm")))
                                            .setEmphasis_first_item(true)
                                            .setContent_item(
                                                    Arrays.asList(
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试内容")
                                                                    .setValue(exam.getName()),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试开始时间")
                                                                    .setValue(examStartTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试结束时间")
                                                                    .setValue(examEndTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试类型")
                                                                    .setValue(examTypeEnum.getName())
                                                    )
                                            )
                            )
            );
        }

        updateExamKVInCache();
        updateCategorysInCache();
        return true;
    }

    @Override
    public Boolean rebuildExam(RebuildExamReq req) {
        Exam examInDb = baseMapper.selectById(req.getExamId());
        if (examInDb == null) {
            throw new RuntimeException("补考考试不存在");
        }
        Integer passScore = examInDb.getPassScore() * 10;
        List<ExamUser> examUsers = examUserMapper.selectList(new LambdaQueryWrapper<ExamUser>().eq(ExamUser::getExamId, req.getExamId()));
        if (CollectionUtil.isEmpty(examUsers)) {
            throw new RuntimeException("未查询到需补考员工");
        }
        List<ExamPaperAnswer> examPaperAnswers = newExamPaperAnswerService.list(new LambdaQueryWrapper<ExamPaperAnswer>().eq(ExamPaperAnswer::getExamId, req.getExamId()));
        // 需补考人员
        List<Long> rebuildUserIds = new ArrayList<>();
        if (CollectionUtil.isEmpty(examPaperAnswers)) {
            rebuildUserIds = examUsers.stream().map(ExamUser::getUserId).collect(Collectors.toList());
        }else {
            Map<Integer, List<ExamPaperAnswer>> answerUserMap = examPaperAnswers.stream().collect(Collectors.groupingBy(ExamPaperAnswer::getCreateUser));
            for (Map.Entry<Integer, List<ExamPaperAnswer>> entry : answerUserMap.entrySet()) {
                Integer answerUserId = entry.getKey();
                List<ExamPaperAnswer> answerList = entry.getValue();
                ExamPaperAnswer examPaperAnswer = answerList.stream().max(Comparator.comparing(ExamPaperAnswer::getUserScore)).get();
                if (examPaperAnswer.getUserScore() < passScore) {
                    rebuildUserIds.add(Long.valueOf(answerUserId));
                }
            }
        }
        if (CollectionUtil.isEmpty(rebuildUserIds)) {
            throw new RuntimeException("未查询到需补考员工");
        }

        // 新建补考考试
        examInDb.setId(null)
                .setName(examInDb.getName() + "【补考】")
                .setStatus(ExamStatusEnum.ING.getCode())
                .setRepetition(true)
                .setLimitCount(req.getLimitCount())
                .setExamType(4)
                .setLimitStartTime(LocalDateTime.parse(req.getLimitDateTime().get(0), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                .setLimitEndTime(LocalDateTime.parse(req.getLimitDateTime().get(1), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                .setTeacherName(examInDb.getTeacherName())
                .setTeacherTime(examInDb.getTeacherTime())
                .insert();

        // 插入中间表
        Set<ExamUser> examUserList = new HashSet<>();
        rebuildUserIds.forEach(userId -> {
            examUserList.add(
                    new ExamUser()
                            .setExamId(examInDb.getId())
                            .setUserId(userId)
                            .setStatus(ExamUserStatusEnum.NOTICED.getCode())
            );
        });
        examUserService.saveBatch(examUserList);

        List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().in(User::getId, rebuildUserIds));
        qywxComponent.sendMsg(
                new MiniProgramNotice()
                        .setTouser(users.stream().map(User::getQywxId).distinct().collect(Collectors.joining("|")))
                        .setMsgtype(QywxMsgType.MINI_PROGRAM_NOTICE.getMsgtype())
                        .setEnable_id_trans(0)
                        .setEnable_duplicate_check(0)
                        .setDuplicate_check_interval(1800)
                        .setMiniprogram_notice(
                                new MiniProgramNotice.Notice()
                                        .setAppid(appid)
                                        .setPage(String.format("pages/exam/do/index?id=%s&examId=%s", examInDb.getPaperId(), examInDb.getId()))
                                        .setTitle("补考通知")
                                        .setDescription(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm")))
                                        .setEmphasis_first_item(true)
                                        .setContent_item(
                                                Arrays.asList(
                                                        new MiniProgramNotice.Notice.KeyVal()
                                                                .setKey("考试内容")
                                                                .setValue(examInDb.getName()),
                                                        new MiniProgramNotice.Notice.KeyVal()
                                                                .setKey("考试开始时间")
                                                                .setValue(examInDb.getLimitStartTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"))),
                                                        new MiniProgramNotice.Notice.KeyVal()
                                                                .setKey("考试结束时间")
                                                                .setValue(examInDb.getLimitEndTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"))),
                                                        new MiniProgramNotice.Notice.KeyVal()
                                                                .setKey("考试类型")
                                                                .setValue("限时补考")
                                                )
                                        )
                        )
        );
        return null;
    }

    @Override
    public Boolean addExamUsers(AddExamUserReq addExamUserReq) {
        Exam examIdDb = baseMapper.selectById(addExamUserReq.getExamId());

        // 插入考试-用户表
        List<Long> userIds = newUserMapper.selectList(
                new LambdaUpdateWrapper<User>()
                        .in(User::getQywxId, addExamUserReq.getUserQywxIds())
                        .eq(User::getStatus, UserStatusEnum.Enable.getCode())
        ).stream().map(user -> user.getId().longValue()).collect(Collectors.toList());

        //查看是否有重复数据
        List<ExamUser> usersInDb = examUserService.list(
                new LambdaQueryWrapper<ExamUser>()
                        .eq(ExamUser::getExamId, addExamUserReq.getExamId())
        );

        HashSet<Long> finalUserIds = new HashSet<>(userIds);
        if (CollectionUtil.isNotEmpty(usersInDb)) {
            finalUserIds.addAll(usersInDb.stream().map(ExamUser::getUserId).collect(Collectors.toList()));
        }

        Set<ExamUser> examUsers = new HashSet<>();
        finalUserIds.forEach(userId -> {
            examUsers.add(
                    new ExamUser()
                            .setExamId(addExamUserReq.getExamId())
                            .setUserId(userId)
                            .setStatus(ExamUserStatusEnum.NOTICED.getCode())
            );
        });
        examUserService.saveBatch(examUsers);

        // 通知员工考试
        if (addExamUserReq.getNotice() == null || addExamUserReq.getNotice()) {
            List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().select(User::getQywxId).in(User::getId, userIds));
            if (CollectionUtil.isEmpty(users)) {
                log.error("通知员工考试，查询对应考试用户为空，msg：{}", JSON.toJSONString(addExamUserReq));
                throw new BusinessException("未查到对应考试用户");
            }
            String examStartTime = null;
            String examEndTime = null;
            switch (ExamTypeEnum.of(examIdDb.getExamType())) {
                // 固定试卷
                case Fixed:
                    // 固定试卷，
                    examStartTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = "长期";
                    break;
                // 时段试卷
                case TimeLimit:
                    examStartTime = examIdDb.getLimitStartTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = examIdDb.getLimitEndTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    break;
                default:
                    break;
            }
            qywxComponent.sendMsg(
                    new MiniProgramNotice()
                            .setTouser(users.stream().map(User::getQywxId).distinct().collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MINI_PROGRAM_NOTICE.getMsgtype())
                            .setEnable_id_trans(0)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMiniprogram_notice(
                                    new MiniProgramNotice.Notice()
                                            .setAppid(appid)
                                            .setPage(String.format("pages/exam/do/index?id=%s&examId=%s", examIdDb.getPaperId(), examIdDb.getId()))
                                            .setTitle("考试通知")
                                            .setDescription(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm")))
                                            .setEmphasis_first_item(true)
                                            .setContent_item(
                                                    Arrays.asList(
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试内容")
                                                                    .setValue(examIdDb.getName()),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试开始时间")
                                                                    .setValue(examStartTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试结束时间")
                                                                    .setValue(examEndTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试类型")
                                                                    .setValue(examIdDb.getName())
                                                    )
                                            )
                            )
            );
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean editExam(EditExamReq editExamReq, Integer operUserId) {
        Exam exam = baseMapper.selectById(editExamReq.getId());
        if (exam == null) {
            throw new BusinessException("编号为" + editExamReq.getId() + "的试卷不存在，请检查核对");
        }

        NewExamPaper examPaper = newExamPaperService.getById(editExamReq.getPaperId());
        if (examPaper == null) {
            throw new BusinessException("编号为" + editExamReq.getPaperId() + "的试卷不存在，请检查核对");
        }
        Exam newExam = new Exam();
        if (editExamReq.getExamType().equals(ExamTypeEnum.TimeLimit.getCode())) {
            LocalDateTime limitStartTime;
            LocalDateTime limitEndTime;
            if (editExamReq.getLimitDateTime() != null && StringUtils.isNotBlank(editExamReq.getLimitDateTime().get(0))) {
                limitStartTime = LocalDateTime.parse(editExamReq.getLimitDateTime().get(0), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            } else {
                throw new BusinessException("考试限制时间不能为空");
            }
            if (editExamReq.getLimitDateTime() != null && StringUtils.isNotBlank(editExamReq.getLimitDateTime().get(1))) {
                limitEndTime = LocalDateTime.parse(editExamReq.getLimitDateTime().get(1), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            }else {
                throw new BusinessException("考试限制时间不能为空");
            }
            newExam.setLimitStartTime(limitStartTime)
                    .setLimitEndTime(limitEndTime);
        }

        BeanUtils.copyProperties(editExamReq, newExam);

        // 时段试卷处理
        if (ExamTypeEnum.TimeLimit.getCode().equals(exam.getExamType())) {
            // 判断时间是否合规
            if (exam.getLimitEndTime().isBefore(LocalDateTime.now())) {
                log.error("时段考试对应考试时段已过，不可发布考试，请调整考试时间段，msg：{}", JSON.toJSONString(examPaper));
                throw new BusinessException("时段考试对应考试时段已过，不可发布考试，请调整考试时间段");
            }
            if (exam.getLimitEndTime().isBefore(exam.getLimitStartTime())) {
                log.error("时段考试对应考试时段不合规，开始考试时间不能在结束考试之后，不可发布考试，请调整考试时间段，msg：{}", JSON.toJSONString(examPaper));
                throw new BusinessException("时段考试对应考试时段不合规，开始考试时间不能在结束考试之后，不可发布考试，请调整考试时间段");
            }
            if (exam.getLimitStartTime().isBefore(LocalDateTime.now())) {
                // 开始时间已过，考试状态为考试中
                newExam.setStatus(ExamStatusEnum.ING.getCode());
            } else {
                // 开始时间未到，考试状态为等待考试
                newExam.setStatus(ExamStatusEnum.WAIT.getCode());
            }
        }

        // 更新主表
        updateById(newExam);

        // 更新子表
        examUserMapper.delete(new LambdaUpdateWrapper<ExamUser>().eq(ExamUser::getExamId, editExamReq.getId()));
        List<Long> userIds = newUserMapper.selectList(
                new LambdaQueryWrapper<User>()
                        .in(User::getQywxId, editExamReq.getUserIds())
                        .eq(User::getStatus, UserStatusEnum.Enable.getCode())
                        .eq(User::getDeleted, false)
        ).stream().map(u -> Long.valueOf(u.getId())).collect(Collectors.toList());
      //  List<Long> userIds = filterDisUser();
        HashSet<ExamUser> examUsers = new HashSet<>();
        userIds.forEach(userId -> {
            examUsers.add(
                    new ExamUser()
                            .setExamId(exam.getId())
                            .setUserId(userId)
                            .setStatus(ExamUserStatusEnum.NOTICED.getCode())
            );
        });

        updateExamKVInCache();
        updateCategorysInCache();
        return examUserService.saveBatch(examUsers);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delExam(Long id, Boolean notice, User user) {
        // 删除考试表
        Exam exam = baseMapper.selectById(id);
        if (exam == null) {
            throw new BusinessException("删除考试不存在，请核对");
        }
        NewExamPaper paper = newExamPaperService.getById(exam.getPaperId());
        removeById(id);

        // 删除中间表
        List<ExamUser> examUsers = examUserMapper.selectList(new LambdaQueryWrapper<ExamUser>().eq(ExamUser::getExamId, id));
        examUserMapper.delete(new LambdaQueryWrapper<ExamUser>().eq(ExamUser::getExamId, id));

        // 通知
        boolean isNotice = paper != null && (notice == null || notice) && (exam.getLimitEndTime() == null || exam.getLimitEndTime().isAfter(LocalDateTime.now()));
        if (isNotice) {
            List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().select(User::getQywxId).in(User::getId, examUsers.stream().map(ExamUser::getUserId).distinct().collect(Collectors.toList())));
            qywxComponent.sendMsg(
                    new MarkdownNotice()
                            .setTouser(users.stream().map(User::getQywxId).collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MARK_DOWN.getMsgtype())
                            .setAgentid(agentId)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMarkdown(
                                    new MarkdownNotice.Notice()
                                            .setContent(
                                                    String.format(
                                                            "考试关闭提醒\n" +
                                                                    ">考试名称：<font color=\"info\">%s</font> \n" +
                                                                    ">下架试卷：`%s`\n" +
                                                                    ">试卷类型：%s\n" +
                                                                    "> \n" +
                                                                    ">下架日期：<font color=\"warning\">%s</font> \n"
                                                            , exam.getName()
                                                            , paper.getName()
                                                            , ExamTypeEnum.of(exam.getExamType()).getName()
                                                            , LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm"))
                                                    )
                                            )
                            )
            );
        }
        updateExamKVInCache();
        updateCategorysInCache();

        // 记录日志
        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
        String content = String.format("%s删除考试【%s】", user.getUserName(), exam.getName());
        userEventLog.setContent(content);
        eventPublisher.publishEvent(new UserEvent(userEventLog));

        return true;
    }

    @Override
    public Boolean closeExam(Long id, Boolean notice) {
        boolean success = updateById(
                new Exam()
                        .setId(id)
                        .setStatus(ExamStatusEnum.DONE.getCode())
        );

        // 通知
        boolean isNotice = success && (notice == null || notice);
        if (isNotice) {
            Exam exam = baseMapper.selectById(id);
            NewExamPaper paper = newExamPaperService.getById(exam.getPaperId());
            List<ExamUser> examUsers = examUserMapper.selectList(new LambdaQueryWrapper<ExamUser>().eq(ExamUser::getExamId, id));
            List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().select(User::getQywxId).in(User::getId, examUsers.stream().map(ExamUser::getUserId).distinct().collect(Collectors.toList())));
            qywxComponent.sendMsg(
                    new MarkdownNotice()
                            .setTouser(users.stream().map(User::getQywxId).collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MARK_DOWN.getMsgtype())
                            .setAgentid(agentId)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMarkdown(
                                    new MarkdownNotice.Notice()
                                            .setContent(
                                                    String.format(
                                                            "考试关闭提醒\n" +
                                                                    ">考试名称：<font color=\"info\">%s</font> \n" +
                                                                    ">下架试卷：`%s`\n" +
                                                                    ">试卷类型：%s\n" +
                                                                    "> \n" +
                                                                    ">下架日期：<font color=\"warning\">%s</font> \n"
                                                            , exam.getName()
                                                            , paper.getName()
                                                            , ExamTypeEnum.of(exam.getExamType()).getName()
                                                            , LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm"))
                                                    )
                                            )
                            )
            );
        }
        return true;
    }

    @Override
    public Boolean openExam(Long id, Boolean notice) {
        Exam exam = baseMapper.selectById(id);
        NewExamPaper paper = newExamPaperService.getById(exam.getPaperId());
        ExamTypeEnum examTypeEnum = ExamTypeEnum.of(exam.getExamType());
        Integer status = null;
        switch (examTypeEnum) {
            // 固定试卷
            case Fixed:
                // 固定试卷，考试状态为考试中，考试状态
                status = ExamStatusEnum.ING.getCode();
                break;
            // 时段试卷
            case TimeLimit:
                if (exam.getLimitEndTime().isBefore(LocalDateTime.now())) {
                    // 结束时间已过，直接完结
                    status = ExamStatusEnum.DONE.getCode();
                } else if (exam.getLimitStartTime().isBefore(LocalDateTime.now())) {
                    // 开始时间已过，考试状态为考试中
                    status = ExamStatusEnum.ING.getCode();
                } else {
                    // 开始时间未到，考试状态为等待考试
                    status = ExamStatusEnum.WAIT.getCode();
                }
                break;
            default:
                break;
        }

        boolean success = updateById(
                new Exam()
                        .setId(id)
                        .setStatus(status)
        );

        // 通知
        boolean isNotice = success && (notice == null || notice);
        if (isNotice) {
            List<ExamUser> examUsers = examUserMapper.selectList(new LambdaQueryWrapper<ExamUser>().eq(ExamUser::getExamId, id));
            List<User> users = newUserMapper.selectList(new LambdaQueryWrapper<User>().select(User::getQywxId).in(User::getId, examUsers.stream().map(ExamUser::getUserId).distinct().collect(Collectors.toList())));
            qywxComponent.sendMsg(
                    new MarkdownNotice()
                            .setTouser(users.stream().map(User::getQywxId).collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MARK_DOWN.getMsgtype())
                            .setAgentid(agentId)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMarkdown(
                                    new MarkdownNotice.Notice()
                                            .setContent(
                                                    String.format(
                                                            "考试开启提醒\n" +
                                                                    ">考试名称：<font color=\"info\">%s</font> \n" +
                                                                    ">开启试卷：`%s`\n" +
                                                                    ">试卷类型：%s\n" +
                                                                    "> \n" +
                                                                    ">开启日期：<font color=\"warning\">%s</font> \n"
                                                            , exam.getName()
                                                            , paper.getName()
                                                            , ExamTypeEnum.of(exam.getExamType()).getName()
                                                            , LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm"))
                                                    )
                                            )
                            )
            );
        }
        return true;
    }

    /**
     * 过滤禁用的用户
     */
    private List<Long> filterDisUser(List<Long> userIds) {
        List<User> disUsers = newUserMapper.selectList(new LambdaQueryWrapper<User>().eq(User::getStatus, UserStatusEnum.Disable.getCode()));
        if (CollectionUtil.isNotEmpty(disUsers)) {
            List<Long> disUserIds = disUsers.stream().map(u -> Long.valueOf(u.getId())).collect(Collectors.toList());
            return userIds.stream().filter(u -> !disUserIds.contains(u)).collect(Collectors.toList());
        }
        return userIds;
    }

    @Override
    public List<IdNameKV> queryExamKVListInCache(String keyword) {
        List<IdNameKV> examKVResList = (List<IdNameKV>) redisTemplate.opsForValue().get(CommonRedisKey.ALL_EXAM_NAME_KEY);
        if (StringUtils.isNotBlank(keyword) && examKVResList != null) {
            examKVResList = examKVResList.stream().filter(examKVRes -> examKVRes.getName().contains(keyword)).collect(Collectors.toList());
        }
        return examKVResList;
    }

    /**
     * 更新缓存
     */
    @Override
    public void updateExamKVInCache() {
        List<IdNameKV> examKVList = baseMapper.queryExamKVList();
        if (CollectionUtil.isNotEmpty(examKVList)) {
            redisTemplate.opsForValue().set(CommonRedisKey.ALL_EXAM_NAME_KEY, examKVList, 30, TimeUnit.DAYS);
        }
    }

    @Override
    public List<IdNameKV> queryUnexaminedList(Integer examId) {
        List<IdNameKV> idNameKVS = baseMapper.queryUnexaminUserKVList(examId);
        if (CollectionUtil.isNotEmpty(idNameKVS)) {
            idNameKVS = idNameKVS.stream().distinct().collect(Collectors.toList());
        }
        return idNameKVS;
    }

    @Override
    public Boolean noticeUnexaminedList(Integer examId) {
        List<UnExamNoticeInfoVM> unExamNoticeInfoVMS = baseMapper.queryUnexaminNoticeInfo(examId);
        if (CollectionUtil.isNotEmpty(unExamNoticeInfoVMS)) {
            UnExamNoticeInfoVM template = unExamNoticeInfoVMS.get(0);
            String examStartTime = null;
            String examEndTime = null;
            ExamTypeEnum examTypeEnum = ExamTypeEnum.of(template.getExamType());
            switch (examTypeEnum) {
                // 固定试卷
                case Fixed:
                    // 固定试卷，
                    examStartTime = template.getExamCreateTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = "长期";
                    break;
                // 时段试卷
                case TimeLimit:
                    examStartTime = template.getLimitStartTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    examEndTime = template.getLimitEndTime().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH点mm分"));
                    break;
                default:
                    break;
            }
            qywxComponent.sendMsg(
                    new MiniProgramNotice()
                            .setTouser(unExamNoticeInfoVMS.stream().map(UnExamNoticeInfoVM::getQywxId).distinct().collect(Collectors.joining("|")))
                            .setMsgtype(QywxMsgType.MINI_PROGRAM_NOTICE.getMsgtype())
                            .setEnable_id_trans(0)
                            .setEnable_duplicate_check(0)
                            .setDuplicate_check_interval(1800)
                            .setMiniprogram_notice(
                                    new MiniProgramNotice.Notice()
                                            .setAppid(appid)
                                            .setPage(String.format("pages/exam/do/index?id=%s&examId=%s", template.getPaperId(), template.getExamId()))
                                            .setTitle("未考提醒")
                                            .setDescription(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM月dd日 HH:mm")))
                                            .setEmphasis_first_item(true)
                                            .setContent_item(
                                                    Arrays.asList(
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试内容")
                                                                    .setValue(template.getExamName()),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试开始时间")
                                                                    .setValue(examStartTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试结束时间")
                                                                    .setValue(examEndTime),
                                                            new MiniProgramNotice.Notice.KeyVal()
                                                                    .setKey("考试类型")
                                                                    .setValue(examTypeEnum.getName())
                                                    )
                                            )
                            )
            );
            return true;
        }else {
            throw new BusinessException("该考试暂无未考人员");
        }
    }

    @Override
    public Boolean scheduleQueryExamRankNotice() {
/*        List<UnNoticeExamInfoVM> unNoticeExamInfos = baseMapper.queryExamNotNoticeList();
        if (CollectionUtil.isEmpty(unNoticeExamInfos)) {
            return true;
        }
        Map<Long, List<UnNoticeExamInfoVM>> examMap = unNoticeExamInfos.stream().collect(Collectors.groupingBy(UnNoticeExamInfoVM::getExamId));
        List<Long> needNoticeExamIdList = new ArrayList();
        examMap.forEach((examId,exams) -> {
            boolean needNotice = false;
            UnNoticeExamInfoVM model = exams.get(0);
            if (ExamTypeEnum.TimeLimit.getCode().equals(model.getExamType()) && LocalDateTime.now().isAfter(model.getLimitEndTime())) {
                // 时段考试，判断时间就行
                needNotice = true;
            }else {
                // 固定考试，判断全部人完成
                needNotice = exams.stream().allMatch(it -> it.getUserDoExamStatus().equals(2));
            }
            if (needNotice) {
                needNoticeExamIdList.add(examId);
            }
        });

        if (CollectionUtil.isNotEmpty(needNoticeExamIdList)) {

        }*/
        return null;
    }
}
